feat(mobile): app badge tracks glanceable needs-input count - #5873
Conversation
…scribed-b-fbf8/s2)
… deliver-the-work-described-b-fbf8/ux2)
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of the main merge restoring idle-window Live Activity eligibility and the publisher needs-input coalesce tests; no issues found. Files Reviewed (11 files)
Previous Review Summaries (4 snapshots, latest commit 2bec1c9)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 2bec1c9)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of idle-only glanceable eligibility, Live Activity/widget count gating, and the launcher badge write guard; no issues found. Files Reviewed (13 files)
Previous review (commit c2ffa8c)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of Files Reviewed (1 file)
Previous review (commit c175a89)Status: No Issues Found | Recommendation: Merge Files Reviewed (18 files)
Previous review (commit edf03a8)Status: No Issues Found | Recommendation: Merge Files Reviewed (13 files)
Reviewed by grok-4.6 · Input: 178.2K · Output: 14.6K · Cached: 455.6K Review guidance: REVIEW.md from base branch |
… (kwf deliver-the-work-described-b-fbf8/vr16)
…-work-described-b-fbf8/vr16)
Resolve the glanceable idle rule in favour of main. Main's merged idle-window design (#5881) keeps a card alive while every agent is idle but never raises one; this branch had made idle end the card at once. Four sites carried the older rule: - packages/app-shared: buildGlanceableSnapshot + isEligibleGlanceableWork - services/notifications: glanceable-delivery + glanceable-refresh - apps/mobile: the widget layout, view-props, and the presentation comment All four now follow main. The branch keeps its own subject, the launcher app badge fed from the glanceable needs-input count, plus the publisher change that emits a needs-input change without the coalesce wait.
Changelog for users
Changelog for maintainers
apps/mobile/src/lib/badge-freshness.tsandapps/mobile/src/lib/badge-hydration.ts.badge: snapshot.needsInput(iOSpriority: high; Androidpriority: default,sound: null,channelId: 'active-agents'), collapsed per scope key viacollapseIdinstead oftag.badgetotal;NotificationChannelDO.incrementBucketnow returnsvoid(bucket accumulation still feeds the unread API).shouldSetBadgefrom whether the glanceable push data was applied and refreshes active sessions from a successfully applied push via the newrefreshActiveSessionsFromPush.useCurrentUserIdkeeps the error state visible during a retry (isError || (isLoading && isFetched)).Merge with main
Main's idle-window design (#5881) landed while this branch was open. It keeps a Live Activity alive
while every agent is idle and never raises one from idle; this branch had made idle end the card at
once. The merge keeps main's rule at all four sites that carried the older one:
app-shared(
buildGlanceableSnapshot,isEligibleGlanceableWork),glanceable-delivery.ts,glanceable-refresh.ts, and the mobile widget layout withview-props.ts.The branch now changes no Live Activity behaviour. Its badge work is unaffected: the badge reads
snapshot.needsInputdirectly. The three tests that asserted the reverted rule are gone (the idlecases in
publisher.test.tsandios-sink.test.ts, andactive-agents-live-activity.test.ts).Every E2E result below about the launcher badge still holds. The two lines that also asserted the
card disappearing on idle are marked above.
Changes by area
Mobile — mark-read / unread-counts paths (
kilo-chat/hooks/mark-read-operation.ts,kilo-chat/hooks/use-mark-read.ts,lib/hooks/use-unread-counts.ts,lib/hooks/use-unread-counts-invalidation.ts): removed all OS badge writes and the badge-freshness epoch from these paths so chat reads only maintain the cache and never touch the launcher badge; deleted the now-unusedbadge-freshness.tsandbadge-hydration.ts.Mobile — glanceable publisher (
lib/glanceable/publisher.ts): a change inneedsInputnow cancels coalescing and emits immediately, so badge changes reach the sinks without the happy-path coalesce delay.Mobile — app badge sink (
lib/notifications.ts): added a glanceable sink that writes the OS badge fromsnapshot.needsInputthrough a serialized, count-change-guarded write (avoiding clobbering a badge iOS applied from a visible push); it applies foreground push badges, waits for a pending badge write before a background task finishes, and clears on terminal snapshots.Mobile — active sessions live sync (
lib/active-sessions-live-sync.ts): exposedrefreshActiveSessionsFromPushso a successfully applied glanceable push triggers a manual refresh.Mobile — current user hook (
lib/hooks/use-current-user-id.ts): keeps the error state visible while a failed request retries.Notifications service — chat push delivery (
dos/NotificationChannelDO.ts): stopped attaching a badge total to chat message pushes; bucket math still accumulates for the unread API.Notifications service — glanceable delivery (
lib/glanceable-delivery.ts):buildGlanceableExpoMessagesnow takes a platform and attachesbadge: snapshot.needsInputto the glanceable push, keeping the badge and snapshot ordered together.E2E proof
Proved live on iOS and Android simulators (backend and mobile-app sections passed;
mobile: typecheck + unit + i18n + unusedgates and the mobile-device verifier passed, spot check clean).Badge follows the needs-input count:
Badge does not track unread chat:
Lifecycle and robustness:
[e1] [iOS and Android] Happy push: Deliver a glanceable snapshot with one question session; the launcher badge shows 1. — Launcher badge none to 1; in-app 1 LIVE (e1-ios-agents-count-1.png, e1-ios.mp4).
e1-ios.mp4.trim.mp4
[e2] [iOS and Android] Empty push: Deliver a glanceable snapshot with zero needs-input sessions; the launcher badge clears. — Launcher badge 0 (e2-badge.png) equals in-app Agents 0 (e2-inapp.png, e2-agents.png); rec e2-ios-trim.mp4.
e2-ios-trim.mp4.trim.mp4
[e3] [iOS and Android] Ordinary push: Deliver a chat message while backgrounded; the launcher badge does not change to the unread total. — Ordinary chat push badge 9 while backgrounded did not set unread 9 (e3-ios-before-badge.png, e3-ios-after-ordinary-push-badge.png); rec e3-ios-ordinary-push.mp4.
e3-ios-ordinary-push.mp4.trim.mp4
Recording of the verified flow (waits trimmed)
e2-ios-ordinary-push-bg-trim.mp4.trim.mp4
[e2] [iOS and Android] Empty push: Deliver a glanceable snapshot with zero needs-input sessions; the launcher badge clears. — iOS launcher badge cleared to 0 after empty snapshot (e2-ios-trimmed.mp4, e2-ios-before-badge.png, e2-ios-badge-cleared.png, e2-ios-inapp.png).
e2-ios-trimmed.mp4.trim.mp4
[e1] [iOS and Android] Foreground push: Deliver a glanceable push with count 2 while open; the launcher badge becomes 2. — iOS: while open, glanceable push count 2 set launcher badge 2 (e1-ios-trim.mp4, e1-ios-home-before.png, e1-ios-fg-banner.png, e1-ios-badge.png); in-app Agents stayed 1 LIVE with no needs-input count (e1-ios-inapp.png). UX-PREEXISTING: [pre-existing] Dynamic Island 1 while launcher clear (e1-ios-home
e1-ios-trim.mp4.trim.mp4
Pre-existing UX defects observed (not changed here)